﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Cherry.Agile.Core;
using Microsoft.Practices.Unity;
using Cherry.Agile.ServiceModel;
using Cherry.Agile.ServiceModel.Runtime;
using System.ServiceModel;
using Cherry.Agile.ServiceModel.Config;
using System.ServiceModel.Channels;
using Cherry.Agile.Core.Instrumentation;

namespace Cherry.Agile.ServiceModel {
    public class ServiceFactory : IServiceFactory {
        private static readonly IClientInstrumentationProvider _instrumentation;

        static ServiceFactory() {
            if (Container.Current.IsRegistered<LifetimeManager>()) {
                var lm = Container.Current.Resolve<LifetimeManager>();
                Container.Current.RegisterType<ClientFactory>(lm);
            } else
                Container.Current.RegisterType<ClientFactory>(new ContainerControlledLifetimeManager());
            if (Container.Current.IsRegistered<IClientInstrumentationProvider>()) {

                _instrumentation = Unity.GetClientInstrumentationProvider();
            }
        }

        string _lifetime;
        IUnityContainer InnerContainer {
            get {
                var val = _manager.GetValue() as IUnityContainer;
                if (val == null) {
                    val = new UnityContainer();
                    _manager.SetValue(val);
                }
                return val;
            }
        }
        static object _sync = new object();
        LifetimeManager _manager ;
        public ServiceFactory(string lifetime) {
            _lifetime = lifetime;//container = _manager.ge
            _manager = GetLifetimeManager();
        }

        public T GetService<T>() where T : class {
            return GetService<T>(string.Empty);
        }


        public T GetService<T>(string name) where T : class {
            if (InnerContainer == null)
                throw new ObjectDisposedException("ServiceFactory");

            if (string.IsNullOrEmpty(name))
                name = typeof(T).Name;
            var obj = default(T);
            lock (_sync) {
                if (InnerContainer.IsRegistered<T>(name)) {
                    obj = InnerContainer.Resolve<T>(name);
                    var channel = obj as IClientChannel;
                    if (Validate(channel))
                        return obj;
                }
            }

            obj = GetChannel<T>(name);

            return obj;
        }

        private bool Validate(IClientChannel channel) {
            if (channel == null)
                return false;
            try {
                if (channel.State > CommunicationState.Opened)
                    return false;
            } catch (ObjectDisposedException) {
                return false;
            }
            return true;
        }

        private T GetChannel<T>(string name) where T : class {
            var obj = default(T);
            lock (_sync) {
                if (InnerContainer.IsRegistered<T>(name))
                    obj = InnerContainer.Resolve<T>(name);
                var channel = obj as IClientChannel;
                if (Validate(channel))
                    return obj;

                var clientFactory = Container.Current.Resolve<ClientFactory>();
                if (ClientConfigHelper.IsEndPointExist(name)) {
                    obj = clientFactory.CreateClient<T>(name);
                } else {
                    var config = ClientConfigHelper.GetConfig<T>();
                    var address = config.GetAddress<T>();

                    if (string.IsNullOrWhiteSpace(address))
                        throw new ArgumentNullException(string.Format("没有找到EndPoint '{0}'对应的配置，请确认EndPoint是否已经正确配置", typeof(T).FullName));

                    var binding = RuntimeUnity.GetDefeaultBinding();

                    obj = clientFactory.CreateClient<T>(binding, new EndpointAddress(address), config.IsBidirectional);
                }
                InnerContainer.RegisterInstance<T>(name, obj, GetLifetimeManager());
            }

            return obj;
        }

        private LifetimeManager GetLifetimeManager() {

            //return new ChannelLifeTimeManager();
            if (string.IsNullOrEmpty(_lifetime))
                return new ChannelLifeTimeManager();
            if (Container.Current.IsRegistered<LifetimeManager>(_lifetime))
                return Container.Current.Resolve<LifetimeManager>(_lifetime);
            else
                return new Microsoft.Practices.Unity.PerResolveLifetimeManager();
        }

        public void Dispose() {
            InnerContainer.Dispose();
        }

        public static void StartService(string baseAdress,
            IServiceFilter filter, IEventListener listener,
            Action<ServiceHost> action, Action<Exception> error = null) {
            var packages = RuntimeUnity.GetServicePackages(baseAdress);
            foreach (var p in packages) {
                try {
                    var runner = new RemoteServiceRunner();
                    runner.Load(p);
                    var el = new EventListener();
                    el.Notify += (o, e) => { if (e.Type == "error")error(e.Exception); };
                    runner.Run(listener ?? el, filter ?? ServiceFilter.Empty);
                } catch (System.ServiceModel.CommunicationException ce) {
                    throw new NotSupportedException("请确认Net.Tcp Port Sharing Service等服务是否开启，以及服务器配置是否正确。", ce);
                } catch (Exception ex) {
                    if (error != null)
                        error(ex);
                }

            }
        }

        #region private methods


        #endregion

        public static void CloseService(IDisposable obj) {
            CloseService(obj, null);
        }

        internal static void CloseService(IDisposable obj, IClientInstrumentationProvider instrumentation) {
            if (instrumentation == null)
                instrumentation = _instrumentation;
            var client = obj as System.ServiceModel.IClientChannel;
            if (client == null)
                return;

            if (client.State == System.ServiceModel.CommunicationState.Faulted) {
                client.Abort();
                if (instrumentation != null)
                    instrumentation.Faulted();
            } else {
                client.Close();
                if (instrumentation != null)
                    instrumentation.Closed();
            }
        }

        public string Name {
            get {
                return "WCF";
            }
        }
        
        public void Teardown() {
            var lf = _manager as ChannelLifeTimeManager;
            if (lf == null)
                return;
            else
                lf.TearDown();
        }
    }
}
